package main

import (
	"fmt"
	"os"
	"strings"
	"time"
	"unicode"

	"log"

	"golang.org/x/crypto/ssh/terminal"
)

const (
	HLNORMAL = iota
	HLNONPRINT
	HLCOMMENT
	HLMLCOMMENT
	HLKEYWORD1
	HLKEYWORD2
	HLSTRING
	HLNUMBER
	HLMATCH

	HLHIGHLIGHTSTRINGS = 1 << 0
	HLHIGHLIGHTNUMBERS = 1 << 1
)

type editorSyntax struct {
	fileMatch              []string
	keywords               []string
	singlelineCommentStart string
	multilineCommentStart  string
	multilineCommentEnd    string
	flags                  int
}

type erow struct {
	idx    int    /* Row index in the file, zero-based. */
	size   int    /* Size of the row, excluding the null term. */
	rsize  int    /* Size of the rendered row. */
	chars  string /* Row content. */
	render string /* Row content "rendered" for screen (for TABs). */
	hl     string /* Syntax highlight type for each character in render.*/
	hlOc   int    /* Row had open comment at end in last syntax highlight check. */
}

type hlcolor struct {
	r, g, b int
}

type editorConfig struct {
	cx, cy        int    /* Cursor x and y position in characters */
	rowoff        int    /* Offset of row displayed. */
	coloff        int    /* Offset of column displayed. */
	screenrows    int    /* Number of rows that we can show */
	screencols    int    /* Number of cols that we can show */
	numrows       int    /* Number of rows */
	rawmode       int    /* Is terminal raw mode enabled? */
	row           *erow  /* Rows */
	dirty         int    /* File modified but not saved. */
	filename      string /* Currently open filename */
	statusmsg     string
	statusmsgTime time.Time
	syntax        *editorSyntax /* Current syntax highlight, or NULL. */
}

var E editorConfig

const (
	KEYNULL   = 0   /* NULL */
	CTRLC     = 3   /* Ctrl-c */
	CTRLD     = 4   /* Ctrl-d */
	CTRLF     = 6   /* Ctrl-f */
	CTRLH     = 8   /* Ctrl-h */
	TAB       = 9   /* Tab */
	CTRLL     = 12  /* Ctrl+l */
	ENTER     = 13  /* Enter */
	CTRLQ     = 17  /* Ctrl-q */
	CTRLS     = 19  /* Ctrl-s */
	CTRLU     = 21  /* Ctrl-u */
	ESC       = 27  /* Escape */
	BACKSPACE = 127 /* Backspace */
	/* The following are just soft codes, not really reported by the
	terminal directly. */
	ARROWLEFT = 1000
	ARROWRIGHT
	ARROWUP
	ARROWDOWN
	DELKEY
	HOMEKEY
	ENDKEY
	PAGEUP
	PAGEDOWN
)

var editorSetStatusMessage func(fmt string, args ...interface{})

/* =========================== Syntax highlights DB =========================
 *
 * In order to add a new syntax, define two arrays with a list of file name
 * matches and keywords. The file name matches are used in order to match
 * a given syntax with a given file name: if a match pattern starts with a
 * dot, it is matched as the last past of the filename, for example ".c".
 * Otherwise the pattern is just searched inside the filenme, like "Makefile").
 *
 * The list of keywords to highlight is just a list of words, however if they
 * a trailing '|' character is added at the end, they are highlighted in
 * a different color, so that you can have two different sets of keywords.
 *
 * Finally add a stanza in the HLDB global variable with two two arrays
 * of strings, and a set of flags in order to enable highlighting of
 * comments and numbers.
 *
 * The characters for single and multi line comments must be exactly two
 * and must be provided as well (see the C language example).
 *
 * There is no support to highlight patterns currently. */

/* C / C++ */
var CHLExtensions = []string{".c", ".cpp"}
var CHLKeywords = []string{"switch", "if", "while", "for", "break", "continue", "return", "else", "struct", "union", "typedef", "static", "enum", "class", "int|", "long|", "double|", "float|", "char|", "unsigned|", "signed|", "void|"}
var HLDB = []editorSyntax{
	{
		/* C / C++ */
		CHLExtensions,
		CHLKeywords,
		"//", "/*", "*/",
		HLHIGHLIGHTSTRINGS | HLHIGHLIGHTNUMBERS,
	},
}

func disableRawMode(fd int, state *terminal.State) {
	if E.rawmode != 0 {
		//tcsetattr(fd,TCSAFLUSH,&orig_termios)
		terminal.Restore(fd, state)
		E.rawmode = 0
	}
}

/* Called at exit to avoid remaining in raw mode. */
func editorAtExit(state *terminal.State) {
	disableRawMode(0, state)
}

func enableRawMode(fd int) (state *terminal.State, err error) {
	if E.rawmode == 0 {
		return nil, nil
	}

	if !terminal.IsTerminal(0) {
		return nil, fmt.Errorf("%q is not terminal", fd)
	}
	//atexit(editorAtExit);
	state, err = terminal.MakeRaw(fd)
	if err != nil {
		panic(err)
	}
	E.rawmode = 1
	return state, nil
}

func readFromStdin(data []byte) int {
	n, err := os.Stdin.Read(data)
	if err != nil {
		log.Fatal(err)
	}
	return n
}

func writeToStdout(data string) int {
	n, err := os.Stdout.WriteString(data)
	if err != nil {
		log.Fatal(err)
	}
	return n
}

/* Read a key from the terminal put in raw mode, trying to handle
 * escape sequences. */
func editorReadKey() int {
	var n int
	var data = make([]byte, 1)
	var seq1 = make([]byte, 1)
	var seq2 = make([]byte, 1)
	var seq3 = make([]byte, 1)
	for n == 0 {
		n = readFromStdin(data)
		if n == -1 {
			return -1
		}
	}
	for {
		key := int(data[0])
		switch key {
		case ESC: /* escape sequence */
			/* If this is just an ESC, we'll timeout here. */
			if readFromStdin(seq1) == 0 {
				return ESC
			}

			if readFromStdin(seq2) == 0 {
				return ESC
			}

			/* ESC [ sequences. */
			if seq1[0] == '[' {
				if seq2[0] >= '0' && seq2[0] <= '9' {
					/* Extended escape, read additional byte. */
					if readFromStdin(seq3) == 0 {
						return ESC
					}

					if seq3[0] == '~' {
						switch seq2[0] {
						case '3':
							return DELKEY
						case '5':
							return PAGEUP
						case '6':
							return PAGEDOWN
						}
					}
				} else {
					switch seq2[0] {
					case 'A':
						return ARROWUP
					case 'B':
						return ARROWDOWN
					case 'C':
						return ARROWRIGHT
					case 'D':
						return ARROWLEFT
					case 'H':
						return HOMEKEY
					case 'F':
						return ENDKEY
					}
				}
			} else if seq1[0] == 'O' {
				switch seq2[0] {
				case 'H':
					return HOMEKEY
				case 'F':
					return ENDKEY
				}
			}
			break
		default:
			return key
		}
	}
}

/* Use the ESC [6n escape sequence to query the horizontal cursor position
 * and return it. On error -1 is returned, on success the position of the
 * cursor is stored at *rows and *cols and 0 is returned. */
func getCursorPosition(rows, cols *int) int {
	n := writeToStdout("\x1b[6n")
	if n != 4 {
		return -1
	}
	var datas []byte
	for i := 0; i < 32; i++ {
		data := make([]byte, 1)
		if readFromStdin(data) != 1 {
			break
		}
		if data[0] == 'R' {
			break
		}
		datas = append(datas, data[0])
	}

	if datas[0] != ESC || datas[1] != '[' {
		return -1
	}

	ns, err := fmt.Sscanf(string(datas[2:]), "%d;%d", rows, cols)
	if err != nil {
		log.Fatal(err)
	}
	if ns != 2 {
		return -1
	}
	return 0
}

/* Try to get the number of columns in the current terminal. If the ioctl()
 * call fails the function will try to query the terminal itself.
 * Returns 0 on success, -1 on error. */
func getWindowSize(rows, cols *int) error {
	width, height, err := terminal.GetSize(1)
	if err != nil {
		return err
	}
	rows = &width
	cols = &height
	return nil
}

/* ====================== Syntax highlight color scheme  ==================== */

func isSeparator(c int) bool {
	return unicode.IsSpace(rune(c)) || strings.IndexRune(",.()+-/*=~%[];", rune(c)) >= 0
}

/* Return true if the specified row last char is part of a multi line comment
 * that starts at this row or at one before, and does not end at the end
 * of the row but spawns to the next row. */
func editorRowHasOpenComment(row *erow) int {
	if row.hl != "" && row.rsize > 1 && row.hl[row.rsize-1] == HLCOMMENT &&
		(row.rsize < 2 || (row.render[row.rsize-2] != '*' ||
			row.render[row.rsize-1] != '/')) {
		return 1
	}
	return 0
}

/* Set every byte of row.hl (that corresponds to every character in the line)
 * to the right syntax highlight type (HL*). */
func editorUpdateSyntax(row *erow) {
	if E.syntax == nil {
		return
	}

	keywords := E.syntax.keywords
	scs := E.syntax.singlelineCommentStart
	mcs := E.syntax.multilineCommentStart
	mce := E.syntax.multilineCommentEnd

	/* Point to the first non-space char. */
	p := row.render
	var i = 0
	for ; isSeparator(int(p[i])); i++ {
		break
	}

	prevSep := 1
	inString := 0
	inComment := 0

	if row.idx > 0 && editorRowHasOpenComment(&E.row[row.idx-1]) {
		inComment = 1
	}

}

/* Select the syntax highlight scheme depending on the filename,
 * setting it in the global state E.syntax. */
func editorSelectSyntaxHightlight(filename string) {
	for s := range HLDB {
		for i := 0; i < len(s.fileMatch); i++ {

		}
	}
}

func initEditor() error {
	E.cx = 0
	E.cy = 0
	E.rowoff = 0
	E.coloff = 0
	E.numrows = 0
	E.row = nil
	E.dirty = 0
	E.filename = ""
	E.syntax = nil
	if err := getWindowSize(&E.screenrows, &E.screencols); err != nil {
		return err
	}
	E.screenrows -= 2 /* Get room for status bar. */
	return nil
}
func main() {
	if len(os.Args) != 2 {
		fmt.Println("Usage: kilo <filename>")
	}
	if err := initEditor(); err != nil {
		log.Fatalln("Unable to query the screen for size (columns / rows), err is ", err)
		return
	}

}
